应用AST技术实现自动化升级React 15至React 16的解决方案
导语
本文通过React语法从v15自动升级为v16的方案,阐述了AST的概念及其在前端项目中的应用与探索,介绍了若干适合AST技术在前端落地的场景。
背景
通常一个中后台系统至少有三到五年的生命周期。
在立项之初一般会采用一些成熟稳健的技术,然而随着时间的流逝,原有技术栈必将逐渐变得老旧、难以维护:比如 vue1.x 升级至 2.x、swift的历次升级。
那么我们应当如何处理这些老旧的代码,是另起炉灶推倒重来,亦或是硬着头皮在原来的基础上继续打补丁?
应用AST技术实现自动化升级React技术栈的解决方案
该方案的主要步骤如下:
通过原始代码生成 AST; 按照一定规则修改裁剪 AST; 根据修改后的 AST 生成新代码。
模块的引入/导出改为 ES6 module 的方式; React15 的 createClass 语法改为 React16 的 class 组件:其中需要重点关注的内容有: a)class语法转换; b)createClass组件内函数 this 自绑定转换; c)getInitialState和getDefaultProps转换为新的state和props声明; 部分生命周期如 componentWillMount 修改为 UNSAFE_componentWillMount;
2、初识AST
我们知道,即使是解释执行的语言,也是需要编译的。词法分析会把语句分解成词法单元,即 Token。语法分析会把 Token 转化成抽象语法树,即 AST。
let a = 2 生成的AST
type值为Program通常表示根节点 body是一个包含多个Statement的数组,每一个Statement相当于一个语句 A)type值为VariableDeclaration说明该语句是个变量声明语句
B)declarations中:
C)VariableDeclarator代表变量声明的描述a)id代表变量名
b)init代表变量初始值
D)kind声明变量类型letsourceType为module代表它是一个模块
3、使用Babel进行代码转换
实际工程中我们不需要从头去实现词法分析器、语法分析器,因为工程化的前端项目都会用到Babel ,而@babel/core 已经为我们提供了强大的parser、traverse等工具,利用它们可以快速生成并修改AST。
在使用这些工具之前,我们先比较一下原始代码和目标代码:
原始代码与目标代码对比
使用 astexplorer 来将原始代码和期望代码生成AST,进行对比:
specifiers表示引入的变量; source表示从哪个源引入。
specifiers.local对应declarations.id; source对应declarations.init.arguments。
以类型 VariableDeclaration 声明的 require 语句需要转化为 ImportDeclaration。 以 VariableDeclaration 声明的 createClass 语句其type需要转化为 ClassDeclaration。 在createClass语法中 JSXElement 上使用诸如onClick等事件来调用成员函数时,this是自绑定的,而Class语法中则需要显式绑定:
处理getInitialState:删除该节点,将该节点内容增加在 constructor 中。 处理getDefaultProps:删除该节点,将该节点内容添加在最外层 body 中,类型为ExpressionStatement。 module.epxorts ExpressionStatement 转为 ExportDefaultDeclaration。
转换流程
部分示例代码如下:
查看输出的代码后发现空格、缩进等格式会有些乱,这是由于我们没有对 start、end 等表示位置的属性进行处理。可以用prettier-eslint进行处理以获得格式化后的代码(prettier也是基于AST实现的)。
这样就实现了老项目焕发新生。
我们在产品技术演进过程中为保证中台系统体验的一致性积累了许多业务组件,因为采用了React16 hooks等特性导致不能在该系统中直接复用,复用成本高; 系统所用到的公共组件散落在各个模板中进行维护,维护成本高; 代码语法与主流技术栈产生了一定程度的割裂,不利于后续维护。
升级后可以直接复用部分业务组件,降低了复用成本、也降低了后续的维护成本; 部分组件可以用业务组件代替、另外部分组件可以提取到业务组件库,进行统一管理,提高了复用率,降低了维护成本; 代码语法已经升级到React16,降低了维护成本。
AST的其它应用场景
在小程序百花齐放的今天,我们的业务需要支持各个小程序平台。如果采用原生开发方案,那么单一功能/组件需要在各个平台重复实现,不仅开发效率低,维护成本也成倍增加。使用AST进行转换可以显著帮助开发者降低开发维护成本。
对比不同小程序的模板文件、样式处理以及属性、事件、生命周期,并且统计出功能近似的部分。通过对比我们可以发现,各个平台提供的主要能力大部分都是接近的,这就是我们能够通过AST进行小程序转换的基础。
另外,在苹果向开发者发布“更新使用网页视图的App”通知的前提下,许多大量使用WebView的App都需要进行更新。如果将原Webview功能用Objective-C/Swift重新实现,那么成本对于大多数团队都高到无法接受。这个时候我们可以考虑使用ReactNative来替换原有Webview,通过AST来自动转换React/Vue组件为ReactNative组件,可以极大的降低切换成本。当然这个方案也有其自身的局限性:
React和Vue存在部分生命周期、高阶函数、Fragment等无法兼容之处; ReactNative的样式仅能支持部分CSS子集,部分样式可能需要修改; 部分Web组件需要重新开发为ReactNative组件。
当我们接手维护或者扩展一个业务项目时,如果其实现逻辑相对复杂,那么即使有完善的文档、注释来支撑,在修改时也需要花费一定的时间精力去梳理。这时候我们可以通过AST获取函数调用关系,再将其通过Markdown生成为时序图。通过时序图来协助我们理清调用逻辑。示例如下:
Markdown代码
上述代码生成的时序图
总结
通过对以上几个方案的分析我们可以看出,所有的解决方案都有其闪光/不足之处,我们工程师的职责就是根据不同的场景定制相对最优的解决方案。这也应了软件工程中的那句老话:“没有银弹”。
随着AST在前端的应用场景越来越多,它的重要性也不断提升,因此前端工程师也有必要掌握AST相关知识,在适合的场景运用这项技术来解决问题。对此,希望这篇文章能够帮助到你。Happy Hacking !
参考文献
作者简介
王亮,本地服务事业群前端工程师,具备JavaScript、Java、Python、Objective-C等语言开发经验。相信并践行“职位有前后端之分但工程师没有”。
END
阅读推荐
前端爬虫攻防之接口签名方案
58商家通Android端WebView加载优化方案
并发在58二手车列表的应用
58App-Android端的动态化框架实践与思考
API管理平台之SCF服务测试篇